/**
 * 
 */
package org.youisoft.summer.evaluator.unit;

import static org.apache.commons.logging.LogFactory.getLog;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.youisoft.summer.evaluator.InvokeException;
import org.youisoft.summer.evaluator.PlaceHolder;
import org.youisoft.summer.evaluator.UnitEnum;
import org.youisoft.summer.evaluator.context.CalContext;
import org.youisoft.summer.evaluator.function.Function;
import org.youisoft.summer.evaluator.operator.Operator;
import org.youisoft.summer.evaluator.operator.base.StringOperator;
import org.youisoft.summer.evaluator.registry.FunctionRegistry;
import org.youisoft.summer.evaluator.util.Ulitity;

/**
 * @author zijiang.jl
 *
 */
public abstract class AbstractUnit<R,P> implements VisitorUnit<R,P>{
	protected static final Log logger=getLog(VisitorUnit.class);
	
	private int funIndex=-1;
	
	private String fName;
	
	private int leftIndex=-1;
	
	private int rightIndex=-1;
	
	private Object source;
	
	private String content;
	
	protected P property;
	
	protected R resource;
	
	protected AtomicBoolean initilize=new AtomicBoolean(false);
	
	private FunctionRegistry registry;

	private VisitorUnit<R,P> parentUnit;
	
	
	private java.util.List<VisitorUnit<R,P>> childUnits=new java.util.ArrayList<VisitorUnit<R,P>>();
	
	private java.lang.ThreadLocal<java.util.List<Operator<?>>> childUnitValues=new java.lang.ThreadLocal<java.util.List<Operator<?>>>();
	
	@Override
	public void init() {
		initilize.set(true);
		
	}
	
	@Override
	public void reInit() {
		this.init();
		
	}
	
	@Override
	public boolean isInit() {
		return this.initilize.get();
	}
	
	protected void checkLocalValue(){
		if(this.childSize()>0){
			if(childUnitValues.get()==null){
				childUnitValues.set(new java.util.ArrayList<Operator<?>>());
			}
			childUnitValues.get().clear();
		}
	}


	@Override
	public <C> Operator<?> calculate(CalContext<C,R,P> context,Operator<?> operator)
			throws InvokeException {
		if(!this.isInit()) throw new InvokeException("please initilize unit!");
		if(context.isNeedIngore()) return Operator.nullInstance;
		return this.doCalculate(context,operator);
		
	}
	





	/**
	 * 
	 * @param <C>
	 * @param context
	 * @param operator
	 * @return
	 * @throws InvokeException
	 */
	public abstract <C> Operator<?> doCalculate(CalContext<C,R,P> context,Operator<?> operator) throws InvokeException;

	

	/**
	 * 
	 * @param <C>
	 * @param context
	 * @param operators
	 * @return
	 * @throws InvokeException
	 */
	public final <C> Operator<?> callFunctionIfNessary(CalContext<C,R,P> context,Operator<?>... operators) throws InvokeException{
		return this.callFunctionIfNessary(context,this.getfName(),this.getUnitType(),operators);
		
	}

	
	public <C> Operator<?> callFunctionIfNessary(CalContext<C,R,P> context,String fName,UnitEnum type,Operator<?>... operators) throws InvokeException{
		return this.callFunctionIfNessary(context, null,fName, type,operators);
	}

	
	/**
	 * 
	 * @param <C>
	 * @param context
	 * @param fName
	 * @param operators
	 * @return
	 */
	public <C> Operator<?> callFunctionIfNessary(CalContext<C,R,P> context,Operator<?> lastOperator,String fName,UnitEnum type,Operator<?>... operators) throws InvokeException{
		if(fName==null||"".equals(fName)) {
			throw new InvokeException("function was null!");
		}
		logger.debug("call function:"+fName);
		Operator<?> result=Operator.nullInstance;
		try{
			Function fun=this.getRegistry().findFunction(fName);
			if(fun==null) throw new InvokeException("the function is missing:"+fName);
			fun.setType(type);
			if(!fun.isIgnore(context)){
				if(lastOperator!=null&&fun.addendumOperator()){
					Operator<?>[] ops=new Operator<?>[operators.length+1];
					ops[operators.length]=lastOperator;
					System.arraycopy(operators, 0, ops, 0, operators.length);
					 if(fun.supportOperator(context,ops)){
							fun.init();
							result=fun.invoke(context,ops);
						}
					
				}else if(fun.supportOperator(context,operators)){
					fun.init();
					result=fun.invoke(context,operators);
				}
			}
		}catch(java.lang.Throwable e){
			context.setThrowable(e);
		}
		return result;
	}

	

	
	@Override
	public <C> Operator<?> convertPlaceHolder(CalContext<C,R,P> context,Operator<?> value){
		if(value instanceof StringOperator){
			StringOperator so=StringOperator.class.cast(value);
			if(this.isPlaceHolder(context,so.value())){
				int idx=Integer.parseInt(so.value().substring(1,so.value().length()-1));
				value=this.getChildValue(idx);
			}else{
				value=this.resolveVarPlaceHolder(context, so);
			}
		}
		return value;
	}
	
	private <C> boolean isPlaceHolder(CalContext<C,R,P> context,String value){
		if((value.startsWith(PlaceHolder.instance.getLeftBracePlaceHolder())&&
				value.endsWith(PlaceHolder.instance.getRightBracePlaceHolder()))
		||(value.startsWith(PlaceHolder.instance.getLeftBracketPlaceHolder())&&
				value.endsWith(PlaceHolder.instance.getRightBracketPlaceHolder()))
				||(value.startsWith(PlaceHolder.instance.getLeftParenthesisPlaceHolder())&&
						value.endsWith(PlaceHolder.instance.getRightParenthesisPlaceHolder()))
		){
			return true;
		}
		return false;
	}
	
	protected <C> Operator<?> resolveVarPlaceHolder(CalContext<C,R,P> context,StringOperator so){
		if(so.value().startsWith(PlaceHolder.instance.getVarPlaceHolder())&&
				so.value().endsWith(PlaceHolder.instance.getVarPlaceHolder())){
			String var=so.value().substring(1,so.value().length()-1);
			Operator<?> op=Operator.class.cast(context.get(var));
			if(op!=null) return op;
			op=context.callback(context.getCallback(), var,this.getResource(), this.getProperty());
			if(op!=null) return op;
		} 
		return so;
	}

	public String getContent() {
		if(this.content!=null) return this.content;
		return this.getContentFromSource(this);
	}
	
	public void setContent(String content) {
		this.content = content;
	}
	
	
	public String[] getExpression(){
		return this.getContent().split(PlaceHolder.instance.getSplitPlaceHolder());
	}

	private String getContentFromSource(VisitorUnit<R,P> unit){
		if(Ulitity.hasFName(unit)) return unit.getSource().toString().substring(unit.getFunIndex(),unit.getRightIndex()+1);
		return unit.getSource().toString().substring(unit.getLeftIndex()+1,unit.getRightIndex());
	}

	public final FunctionRegistry getRegistry() {
		return registry;
	}

	public final void setRegistry(FunctionRegistry registry) {
		this.registry = registry;
	}
	
	
	public P getProperty() {
		return property;
	}

	public void setProperty(P property) {
		this.property = property;
	}

	public R getResource() {
		return resource;
	}

	public void setResource(R resource) {
		this.resource = resource;
	}

	@Override
	public void setLeftIndex(int idx) {
		this.leftIndex=idx;
		
	}

	@Override
	public int getLeftIndex() {
	
		return this.leftIndex;
	}

	@Override
	public int getRightIndex() {
	
		return this.rightIndex;
	}

	@Override
	public void setRightIndex(int idx) {
		this.rightIndex=idx;
		
	}
	

	@Override
	public void setSource(Object obj) {
		this.source=obj;
		
	}


	@Override
	public Object getSource() {
		return this.source;
	}

	@Override
	public final VisitorUnit<R, P> getParentUnit() {
		return parentUnit;
	}

	@Override
	public final void setParentUnit(VisitorUnit<R, P> parentUnit) {
		this.parentUnit = parentUnit;
	}
	

	@Override
	public Iterator<VisitorUnit<R, P>> iterator() {
		return new interIterator();
	}
	
	class interIterator implements Iterator<VisitorUnit<R, P>>{
		private java.util.concurrent.atomic.AtomicInteger ai=new java.util.concurrent.atomic.AtomicInteger(-1);
		@Override
		public UnitEnum hasNext() {
			if(ai.get()+2>AbstractUnit.this.childUnits.size()) return null;
			return AbstractUnit.this.childUnits.get(ai.get()+1).getUnitType();
		}

		@Override
		public VisitorUnit<R, P> next() {
			return AbstractUnit.this.childUnits.get(ai.incrementAndGet());
		}

		@Override
		public int currentPosition() {
			return ai.get();
		}

		@Override
		public int size() {
			return AbstractUnit.this.childUnits.size();
		}
		
	}

	@Override
	public VisitorUnit<R, P> getChildUnit(int index) {
		return this.childUnits.get(index);
	}
	
	@Override
	public Operator<?> getChildValue(int index) {
		return this.childUnitValues.get().get(index);
	}

	@Override
	public void addChildUnit(VisitorUnit<R, P> child) {
		this.childUnits.add(child);
	}
	
	

	@Override
	public void addChildValue(Operator<?> value) {
		this.childUnitValues.get().add(value);
		
	}

	@Override
	public int childSize() {
		return this.childUnits.size();
	}

	@Override
	public String getfName() {
		return fName;
	}

	@Override
	public void setfName(String fName) {
		this.fName = fName;
	}

	@Override
	public int getFunIndex() {
		return this.funIndex;
	}

	@Override
	public void setFunIndex(int funIndex) {
		this.funIndex=funIndex;
	}
	//==================================================


	
	
}
